/*
 * @Author: mrdoob / http://mrdoob.com/
 * @Date:   2018-05-08 19:07:58
 * @Last Modified by:   xiaoyangzhao
 * @Last Modified time: 2018-05-08 19:08:36
 */

export default function CanvasRenders(THREE, callback) {
	THREE.SpriteCanvasMaterial = function(parameters) {
		THREE.Material.call(this)

		this.type = 'SpriteCanvasMaterial'

		this.color = new THREE.Color(0xffffff)
		this.program = function() {}

		this.setValues(parameters)
	}

	THREE.SpriteCanvasMaterial.prototype = Object.create(THREE.Material.prototype)
	THREE.SpriteCanvasMaterial.prototype.constructor = THREE.SpriteCanvasMaterial
	THREE.SpriteCanvasMaterial.prototype.isSpriteCanvasMaterial = true

	THREE.SpriteCanvasMaterial.prototype.clone = function() {
		var material = new THREE.SpriteCanvasMaterial()

		material.copy(this)
		material.color.copy(this.color)
		material.program = this.program

		return material
	}

	THREE.CanvasRenderer = function(parameters) {
		// console.log( 'THREE.CanvasRenderer', THREE.REVISION );

		parameters = parameters || {}

		var _this = this
			var _renderData; var _elements; var _lights
			var _projector = new THREE.Projector()

			var _canvas = parameters.canvas !== undefined
			? parameters.canvas
			: document.createElement('canvas')

			var _canvasWidth = _canvas.width
			var _canvasHeight = _canvas.height
			var _canvasWidthHalf = Math.floor(_canvasWidth / 2)
			var _canvasHeightHalf = Math.floor(_canvasHeight / 2)

			var _viewportX = 0
			var _viewportY = 0
			var _viewportWidth = _canvasWidth
			var _viewportHeight = _canvasHeight

			var _pixelRatio = 1

			var _context = _canvas.getContext('2d', {
				alpha: parameters.alpha === true
			})

			var _clearColor = new THREE.Color(0x000000)
			var _clearAlpha = parameters.alpha === true ? 0 : 1

			var _contextGlobalAlpha = 1
			var _contextGlobalCompositeOperation = 0
			var _contextStrokeStyle = null
			var _contextFillStyle = null
			var _contextLineWidth = null
			var _contextLineCap = null
			var _contextLineJoin = null
			var _contextLineDash = []

			var _v1; var _v2; var _v3

			var _v1x; var _v1y; var _v2x; var _v2y; var _v3x; var _v3y

			var _color = new THREE.Color()

			var _diffuseColor = new THREE.Color()
			var _emissiveColor = new THREE.Color()

			var _lightColor = new THREE.Color()

			var _patterns = {}

			var _uvs
			var _uv1x; var _uv1y; var _uv2x; var _uv2y; var _uv3x; var _uv3y

			var _clipBox = new THREE.Box2()
			var _clearBox = new THREE.Box2()
			var _elemBox = new THREE.Box2()

			var _ambientLight = new THREE.Color()
			var _directionalLights = new THREE.Color()
			var _pointLights = new THREE.Color()

			var _vector3 = new THREE.Vector3() // Needed for PointLight
			var _centroid = new THREE.Vector3()
			var _normal = new THREE.Vector3()
			var _normalViewMatrix = new THREE.Matrix3()

		/* TODO
		_canvas.mozImageSmoothingEnabled = false;
		_canvas.webkitImageSmoothingEnabled = false;
		_canvas.msImageSmoothingEnabled = false;
		_canvas.imageSmoothingEnabled = false;
		*/

		// dash+gap fallbacks for Firefox and everything else

		if (_context.setLineDash === undefined) {
			_context.setLineDash = function() {}
		}

		this.domElement = _canvas

		this.autoClear = true
		this.sortObjects = true
		this.sortElements = true

		this.info = {

			render: {

				vertices: 0,
				faces: 0

			}

		}

		// API

		this.getContext = function() {
			return _context
		}

		this.getContextAttributes = function() {
			return _context.getContextAttributes()
		}

		this.getPixelRatio = function() {
			return _pixelRatio
		}

		this.setPixelRatio = function(value) {
			if (value !== undefined) _pixelRatio = value
		}

		this.setSize = function(width, height, updateStyle) {
			_canvasWidth = width * _pixelRatio
			_canvasHeight = height * _pixelRatio

			_canvas.width = _canvasWidth
			_canvas.height = _canvasHeight

			_canvasWidthHalf = Math.floor(_canvasWidth / 2)
			_canvasHeightHalf = Math.floor(_canvasHeight / 2)

			if (updateStyle !== false) {
				_canvas.style.width = width + 'px'
				_canvas.style.height = height + 'px'
			}

			_clipBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf)
			_clipBox.max.set(_canvasWidthHalf, _canvasHeightHalf)

			_clearBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf)
			_clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf)

			_contextGlobalAlpha = 1
			_contextGlobalCompositeOperation = 0
			_contextStrokeStyle = null
			_contextFillStyle = null
			_contextLineWidth = null
			_contextLineCap = null
			_contextLineJoin = null

			this.setViewport(0, 0, width, height)
		}

		this.setViewport = function(x, y, width, height) {
			_viewportX = x * _pixelRatio
			_viewportY = y * _pixelRatio

			_viewportWidth = width * _pixelRatio
			_viewportHeight = height * _pixelRatio
		}

		this.setScissor = function() {}
		this.setScissorTest = function() {}

		this.setClearColor = function(color, alpha) {
			_clearColor.set(color)
			_clearAlpha = alpha !== undefined ? alpha : 1

			_clearBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf)
			_clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf)
		}

		this.setClearColorHex = function(hex, alpha) {
			console.warn('THREE.CanvasRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.')
			this.setClearColor(hex, alpha)
		}

		this.getClearColor = function() {
			return _clearColor
		}

		this.getClearAlpha = function() {
			return _clearAlpha
		}

		this.getMaxAnisotropy = function() {
			return 0
		}

		this.clear = function() {
			if (_clearBox.isEmpty() === false) {
				_clearBox.intersect(_clipBox)
				_clearBox.expandByScalar(2)

				_clearBox.min.x = _clearBox.min.x + _canvasWidthHalf
				_clearBox.min.y = -_clearBox.min.y + _canvasHeightHalf // higher y value !
				_clearBox.max.x = _clearBox.max.x + _canvasWidthHalf
				_clearBox.max.y = -_clearBox.max.y + _canvasHeightHalf // lower y value !

				if (_clearAlpha < 1) {
					_context.clearRect(
						_clearBox.min.x | 0,
						_clearBox.max.y | 0,
						(_clearBox.max.x - _clearBox.min.x) | 0,
						(_clearBox.min.y - _clearBox.max.y) | 0
					)
				}

				if (_clearAlpha > 0) {
					setOpacity(1)
					setBlending(THREE.NormalBlending)

					setFillStyle('rgba(' + Math.floor(_clearColor.r * 255) + ',' + Math.floor(_clearColor.g * 255) + ',' + Math.floor(_clearColor.b * 255) + ',' + _clearAlpha + ')')

					_context.fillRect(
						_clearBox.min.x | 0,
						_clearBox.max.y | 0,
						(_clearBox.max.x - _clearBox.min.x) | 0,
						(_clearBox.min.y - _clearBox.max.y) | 0
					)
				}

				_clearBox.makeEmpty()
			}
		}

		// compatibility

		this.clearColor = function() {}
		this.clearDepth = function() {}
		this.clearStencil = function() {}

		this.render = function(scene, camera) {
			if (camera.isCamera === undefined) {
				console.error('THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.')
				return
			}

			var background = scene.background

			if (background && background.isColor) {
				setOpacity(1)
				setBlending(THREE.NormalBlending)

				setFillStyle(background.getStyle())
				_context.fillRect(0, 0, _canvasWidth, _canvasHeight)
			} else if (this.autoClear === true) {
				this.clear()
			}

			_this.info.render.vertices = 0
			_this.info.render.faces = 0

			_context.setTransform(_viewportWidth / _canvasWidth, 0, 0, -_viewportHeight / _canvasHeight, _viewportX, _canvasHeight - _viewportY)
			_context.translate(_canvasWidthHalf, _canvasHeightHalf)

			_renderData = _projector.projectScene(scene, camera, this.sortObjects, this.sortElements)
			_elements = _renderData.elements
			_lights = _renderData.lights

			_normalViewMatrix.getNormalMatrix(camera.matrixWorldInverse)

			/* DEBUG
			setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
			_context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
			*/

			calculateLights()

			for (var e = 0, el = _elements.length; e < el; e++) {
				var element = _elements[e]

				var material = element.material

				if (material === undefined || material.opacity === 0) continue

				_elemBox.makeEmpty()

				if (element instanceof THREE.RenderableSprite) {
					_v1 = element
					_v1.x *= _canvasWidthHalf
					_v1.y *= _canvasHeightHalf

					renderSprite(_v1, element, material)
				} else if (element instanceof THREE.RenderableLine) {
					_v1 = element.v1
					_v2 = element.v2

					_v1.positionScreen.x *= _canvasWidthHalf
					_v1.positionScreen.y *= _canvasHeightHalf
					_v2.positionScreen.x *= _canvasWidthHalf
					_v2.positionScreen.y *= _canvasHeightHalf

					_elemBox.setFromPoints([
						_v1.positionScreen,
						_v2.positionScreen
					])

					if (_clipBox.intersectsBox(_elemBox) === true) {
						renderLine(_v1, _v2, element, material)
					}
				} else if (element instanceof THREE.RenderableFace) {
					_v1 = element.v1
					_v2 = element.v2
					_v3 = element.v3

					if (_v1.positionScreen.z < -1 || _v1.positionScreen.z > 1) continue
					if (_v2.positionScreen.z < -1 || _v2.positionScreen.z > 1) continue
					if (_v3.positionScreen.z < -1 || _v3.positionScreen.z > 1) continue

					_v1.positionScreen.x *= _canvasWidthHalf
					_v1.positionScreen.y *= _canvasHeightHalf
					_v2.positionScreen.x *= _canvasWidthHalf
					_v2.positionScreen.y *= _canvasHeightHalf
					_v3.positionScreen.x *= _canvasWidthHalf
					_v3.positionScreen.y *= _canvasHeightHalf

					if (material.overdraw > 0) {
						expand(_v1.positionScreen, _v2.positionScreen, material.overdraw)
						expand(_v2.positionScreen, _v3.positionScreen, material.overdraw)
						expand(_v3.positionScreen, _v1.positionScreen, material.overdraw)
					}

					_elemBox.setFromPoints([
						_v1.positionScreen,
						_v2.positionScreen,
						_v3.positionScreen
					])

					if (_clipBox.intersectsBox(_elemBox) === true) {
						renderFace3(_v1, _v2, _v3, 0, 1, 2, element, material)
					}
				}

				/* DEBUG
				setLineWidth( 1 );
				setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
				_context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
				*/

				_clearBox.union(_elemBox)
			}

			/* DEBUG
			setLineWidth( 1 );
			setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
			_context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
			*/

			_context.setTransform(1, 0, 0, 1, 0, 0)
		}

		//

		function calculateLights() {
			_ambientLight.setRGB(0, 0, 0)
			_directionalLights.setRGB(0, 0, 0)
			_pointLights.setRGB(0, 0, 0)

			for (var l = 0, ll = _lights.length; l < ll; l++) {
				var light = _lights[l]
				var lightColor = light.color

				if (light.isAmbientLight) {
					_ambientLight.add(lightColor)
				} else if (light.isDirectionalLight) {
					// for sprites

					_directionalLights.add(lightColor)
				} else if (light.isPointLight) {
					// for sprites

					_pointLights.add(lightColor)
				}
			}
		}

		function calculateLight(position, normal, color) {
			for (var l = 0, ll = _lights.length; l < ll; l++) {
				var light = _lights[l]

				_lightColor.copy(light.color)

				if (light.isDirectionalLight) {
					var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld).normalize()

					var amount = normal.dot(lightPosition)

					if (amount <= 0) continue

					amount *= light.intensity

					color.add(_lightColor.multiplyScalar(amount))
				} else if (light.isPointLight) {
					var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld)

					var amount = normal.dot(_vector3.subVectors(lightPosition, position).normalize())

					if (amount <= 0) continue

					amount *= light.distance == 0 ? 1 : 1 - Math.min(position.distanceTo(lightPosition) / light.distance, 1)

					if (amount == 0) continue

					amount *= light.intensity

					color.add(_lightColor.multiplyScalar(amount))
				}
			}
		}

		function renderSprite(v1, element, material) {
			setOpacity(material.opacity)
			setBlending(material.blending)

			var scaleX = element.scale.x * _canvasWidthHalf
			var scaleY = element.scale.y * _canvasHeightHalf

			var dist = Math.sqrt(scaleX * scaleX + scaleY * scaleY) // allow for rotated sprite
			_elemBox.min.set(v1.x - dist, v1.y - dist)
			_elemBox.max.set(v1.x + dist, v1.y + dist)

			if (material.isSpriteMaterial) {
				var texture = material.map

				if (texture !== null) {
					var pattern = _patterns[texture.id]

					if (pattern === undefined || pattern.version !== texture.version) {
						pattern = textureToPattern(texture)
						_patterns[texture.id] = pattern
					}

					if (pattern.canvas !== undefined) {
						setFillStyle(pattern.canvas)

						var bitmap = texture.image

						var ox = bitmap.width * texture.offset.x
						var oy = bitmap.height * texture.offset.y

						var sx = bitmap.width * texture.repeat.x
						var sy = bitmap.height * texture.repeat.y

						var cx = scaleX / sx
						var cy = scaleY / sy

						_context.save()
						_context.translate(v1.x, v1.y)
						if (material.rotation !== 0) _context.rotate(material.rotation)
						_context.translate(-scaleX / 2, -scaleY / 2)
						_context.scale(cx, cy)
						_context.translate(-ox, -oy)
						_context.fillRect(ox, oy, sx, sy)
						_context.restore()
					}
				} else {
					// no texture

					setFillStyle(material.color.getStyle())

					_context.save()
					_context.translate(v1.x, v1.y)
					if (material.rotation !== 0) _context.rotate(material.rotation)
					_context.scale(scaleX, -scaleY)
					_context.fillRect(-0.5, -0.5, 1, 1)
					_context.restore()
				}
			} else if (material.isSpriteCanvasMaterial) {
				setStrokeStyle(material.color.getStyle())
				setFillStyle(material.color.getStyle())

				_context.save()
				_context.translate(v1.x, v1.y)
				if (material.rotation !== 0) _context.rotate(material.rotation)
				_context.scale(scaleX, scaleY)

				material.program(_context)

				_context.restore()
			} else if (material.isPointsMaterial) {
				setFillStyle(material.color.getStyle())

				_context.save()
				_context.translate(v1.x, v1.y)
				if (material.rotation !== 0) _context.rotate(material.rotation)
				_context.scale(scaleX * material.size, -scaleY * material.size)
				_context.fillRect(-0.5, -0.5, 1, 1)
				_context.restore()
			}

			/* DEBUG
			setStrokeStyle( 'rgb(255,255,0)' );
			_context.beginPath();
			_context.moveTo( v1.x - 10, v1.y );
			_context.lineTo( v1.x + 10, v1.y );
			_context.moveTo( v1.x, v1.y - 10 );
			_context.lineTo( v1.x, v1.y + 10 );
			_context.stroke();
			*/
		}

		function renderLine(v1, v2, element, material) {
			setOpacity(material.opacity)
			setBlending(material.blending)

			_context.beginPath()
			_context.moveTo(v1.positionScreen.x, v1.positionScreen.y)
			_context.lineTo(v2.positionScreen.x, v2.positionScreen.y)

			if (material.isLineBasicMaterial) {
				setLineWidth(material.linewidth)
				setLineCap(material.linecap)
				setLineJoin(material.linejoin)

				if (material.vertexColors !== THREE.VertexColors) {
					setStrokeStyle(material.color.getStyle())
				} else {
					var colorStyle1 = element.vertexColors[0].getStyle()
					var colorStyle2 = element.vertexColors[1].getStyle()

					if (colorStyle1 === colorStyle2) {
						setStrokeStyle(colorStyle1)
					} else {
						try {
							var grad = _context.createLinearGradient(
								v1.positionScreen.x,
								v1.positionScreen.y,
								v2.positionScreen.x,
								v2.positionScreen.y
							)
							grad.addColorStop(0, colorStyle1)
							grad.addColorStop(1, colorStyle2)
						} catch (exception) {
							grad = colorStyle1
						}

						setStrokeStyle(grad)
					}
				}

				if (material.isLineDashedMaterial) {
					setLineDash([material.dashSize, material.gapSize])
				}

				_context.stroke()
				_elemBox.expandByScalar(material.linewidth * 2)

				if (material.isLineDashedMaterial) {
					setLineDash([])
				}
			}
		}

		function renderFace3(v1, v2, v3, uv1, uv2, uv3, element, material) {
			_this.info.render.vertices += 3
			_this.info.render.faces++

			setOpacity(material.opacity)
			setBlending(material.blending)

			_v1x = v1.positionScreen.x
			_v1y = v1.positionScreen.y
			_v2x = v2.positionScreen.x
			_v2y = v2.positionScreen.y
			_v3x = v3.positionScreen.x
			_v3y = v3.positionScreen.y

			drawTriangle(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y)

			if ((material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) && material.map === null) {
				_diffuseColor.copy(material.color)
				_emissiveColor.copy(material.emissive)

				if (material.vertexColors === THREE.FaceColors) {
					_diffuseColor.multiply(element.color)
				}

				_color.copy(_ambientLight)

				_centroid.copy(v1.positionWorld).add(v2.positionWorld).add(v3.positionWorld).divideScalar(3)

				calculateLight(_centroid, element.normalModel, _color)

				_color.multiply(_diffuseColor).add(_emissiveColor)

				material.wireframe === true
					? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
					: fillPath(_color)
			} else if (material.isMeshBasicMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) {
				if (material.map !== null) {
					var mapping = material.map.mapping

					if (mapping === THREE.UVMapping) {
						_uvs = element.uvs
						patternPath(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[uv1].x, _uvs[uv1].y, _uvs[uv2].x, _uvs[uv2].y, _uvs[uv3].x, _uvs[uv3].y, material.map)
					}
				} else if (material.envMap !== null) {
					if (material.envMap.mapping === THREE.SphericalReflectionMapping) {
						_normal.copy(element.vertexNormalsModel[uv1]).applyMatrix3(_normalViewMatrix)
						_uv1x = 0.5 * _normal.x + 0.5
						_uv1y = 0.5 * _normal.y + 0.5

						_normal.copy(element.vertexNormalsModel[uv2]).applyMatrix3(_normalViewMatrix)
						_uv2x = 0.5 * _normal.x + 0.5
						_uv2y = 0.5 * _normal.y + 0.5

						_normal.copy(element.vertexNormalsModel[uv3]).applyMatrix3(_normalViewMatrix)
						_uv3x = 0.5 * _normal.x + 0.5
						_uv3y = 0.5 * _normal.y + 0.5

						patternPath(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap)
					}
				} else {
					_color.copy(material.color)

					if (material.vertexColors === THREE.FaceColors) {
						_color.multiply(element.color)
					}

					material.wireframe === true
						? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
						: fillPath(_color)
				}
			} else if (material.isMeshNormalMaterial) {
				_normal.copy(element.normalModel).applyMatrix3(_normalViewMatrix)

				_color.setRGB(_normal.x, _normal.y, _normal.z).multiplyScalar(0.5).addScalar(0.5)

				material.wireframe === true
					? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
					: fillPath(_color)
			} else {
				_color.setRGB(1, 1, 1)

				material.wireframe === true
					? strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin)
					: fillPath(_color)
			}
		}

		//

		function drawTriangle(x0, y0, x1, y1, x2, y2) {
			_context.beginPath()
			_context.moveTo(x0, y0)
			_context.lineTo(x1, y1)
			_context.lineTo(x2, y2)
			_context.closePath()
		}

		function strokePath(color, linewidth, linecap, linejoin) {
			setLineWidth(linewidth)
			setLineCap(linecap)
			setLineJoin(linejoin)
			setStrokeStyle(color.getStyle())

			_context.stroke()

			_elemBox.expandByScalar(linewidth * 2)
		}

		function fillPath(color) {
			setFillStyle(color.getStyle())
			_context.fill()
		}

		function textureToPattern(texture) {
			if (texture.version === 0 ||
				texture instanceof THREE.CompressedTexture ||
				texture instanceof THREE.DataTexture) {
				return {
					canvas: undefined,
					version: texture.version
				}
			}

			var image = texture.image

			if (image.complete === false) {
				return {
					canvas: undefined,
					version: 0
				}
			}

			var repeatX = texture.wrapS === THREE.RepeatWrapping || texture.wrapS === THREE.MirroredRepeatWrapping
			var repeatY = texture.wrapT === THREE.RepeatWrapping || texture.wrapT === THREE.MirroredRepeatWrapping

			var mirrorX = texture.wrapS === THREE.MirroredRepeatWrapping
			var mirrorY = texture.wrapT === THREE.MirroredRepeatWrapping

			//

			var canvas = document.createElement('canvas')
			canvas.width = image.width * (mirrorX ? 2 : 1)
			canvas.height = image.height * (mirrorY ? 2 : 1)

			var context = canvas.getContext('2d')
			context.setTransform(1, 0, 0, -1, 0, image.height)
			context.drawImage(image, 0, 0)

			if (mirrorX === true) {
				context.setTransform(-1, 0, 0, -1, image.width, image.height)
				context.drawImage(image, -image.width, 0)
			}

			if (mirrorY === true) {
				context.setTransform(1, 0, 0, 1, 0, 0)
				context.drawImage(image, 0, image.height)
			}

			if (mirrorX === true && mirrorY === true) {
				context.setTransform(-1, 0, 0, 1, image.width, 0)
				context.drawImage(image, -image.width, image.height)
			}

			var repeat = 'no-repeat'

			if (repeatX === true && repeatY === true) {
				repeat = 'repeat'
			} else if (repeatX === true) {
				repeat = 'repeat-x'
			} else if (repeatY === true) {
				repeat = 'repeat-y'
			}

			var pattern = _context.createPattern(canvas, repeat)

			if (texture.onUpdate) texture.onUpdate(texture)

			return {
				canvas: pattern,
				version: texture.version
			}
		}

		function patternPath(x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture) {
			var pattern = _patterns[texture.id]

			if (pattern === undefined || pattern.version !== texture.version) {
				pattern = textureToPattern(texture)
				_patterns[texture.id] = pattern
			}

			if (pattern.canvas !== undefined) {
				setFillStyle(pattern.canvas)
			} else {
				setFillStyle('rgba( 0, 0, 0, 1)')
				_context.fill()
				return
			}

			// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120

			var a; var b; var c; var d; var e; var f; var det; var idet
				var offsetX = texture.offset.x / texture.repeat.x
				var offsetY = texture.offset.y / texture.repeat.y
				var width = texture.image.width * texture.repeat.x
				var height = texture.image.height * texture.repeat.y

			u0 = (u0 + offsetX) * width
			v0 = (v0 + offsetY) * height

			u1 = (u1 + offsetX) * width
			v1 = (v1 + offsetY) * height

			u2 = (u2 + offsetX) * width
			v2 = (v2 + offsetY) * height

			x1 -= x0
			y1 -= y0
			x2 -= x0
			y2 -= y0

			u1 -= u0
			v1 -= v0
			u2 -= u0
			v2 -= v0

			det = u1 * v2 - u2 * v1

			if (det === 0) return

			idet = 1 / det

			a = (v2 * x1 - v1 * x2) * idet
			b = (v2 * y1 - v1 * y2) * idet
			c = (u1 * x2 - u2 * x1) * idet
			d = (u1 * y2 - u2 * y1) * idet

			e = x0 - a * u0 - c * v0
			f = y0 - b * u0 - d * v0

			_context.save()
			_context.transform(a, b, c, d, e, f)
			_context.fill()
			_context.restore()
		}

		/*
		function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {

			// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120

			var a, b, c, d, e, f, det, idet,
			width = image.width - 1,
			height = image.height - 1;

			u0 *= width; v0 *= height;
			u1 *= width; v1 *= height;
			u2 *= width; v2 *= height;

			x1 -= x0; y1 -= y0;
			x2 -= x0; y2 -= y0;

			u1 -= u0; v1 -= v0;
			u2 -= u0; v2 -= v0;

			det = u1 * v2 - u2 * v1;

			idet = 1 / det;

			a = ( v2 * x1 - v1 * x2 ) * idet;
			b = ( v2 * y1 - v1 * y2 ) * idet;
			c = ( u1 * x2 - u2 * x1 ) * idet;
			d = ( u1 * y2 - u2 * y1 ) * idet;

			e = x0 - a * u0 - c * v0;
			f = y0 - b * u0 - d * v0;

			_context.save();
			_context.transform( a, b, c, d, e, f );
			_context.clip();
			_context.drawImage( image, 0, 0 );
			_context.restore();

		}
		*/

		// Hide anti-alias gaps

		function expand(v1, v2, pixels) {
			var x = v2.x - v1.x
				var y = v2.y - v1.y
				var det = x * x + y * y
				var idet

			if (det === 0) return

			idet = pixels / Math.sqrt(det)

			x *= idet
			y *= idet

			v2.x += x
			v2.y += y
			v1.x -= x
			v1.y -= y
		}

		// Context cached methods.

		function setOpacity(value) {
			if (_contextGlobalAlpha !== value) {
				_context.globalAlpha = value
				_contextGlobalAlpha = value
			}
		}

		function setBlending(value) {
			if (_contextGlobalCompositeOperation !== value) {
				if (value === THREE.NormalBlending) {
					_context.globalCompositeOperation = 'source-over'
				} else if (value === THREE.AdditiveBlending) {
					_context.globalCompositeOperation = 'lighter'
				} else if (value === THREE.SubtractiveBlending) {
					_context.globalCompositeOperation = 'darker'
				} else if (value === THREE.MultiplyBlending) {
					_context.globalCompositeOperation = 'multiply'
				}

				_contextGlobalCompositeOperation = value
			}
		}

		function setLineWidth(value) {
			if (_contextLineWidth !== value) {
				_context.lineWidth = value
				_contextLineWidth = value
			}
		}

		function setLineCap(value) {
			// "butt", "round", "square"

			if (_contextLineCap !== value) {
				_context.lineCap = value
				_contextLineCap = value
			}
		}

		function setLineJoin(value) {
			// "round", "bevel", "miter"

			if (_contextLineJoin !== value) {
				_context.lineJoin = value
				_contextLineJoin = value
			}
		}

		function setStrokeStyle(value) {
			if (_contextStrokeStyle !== value) {
				_context.strokeStyle = value
				_contextStrokeStyle = value
			}
		}

		function setFillStyle(value) {
			if (_contextFillStyle !== value) {
				_context.fillStyle = value
				_contextFillStyle = value
			}
		}

		function setLineDash(value) {
			if (_contextLineDash.length !== value.length) {
				_context.setLineDash(value)
				_contextLineDash = value
			}
		}
	}

	callback && callback(THREE)
}
